// Copyright 2018 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef V8_OBJECTS_JS_WEAK_REFS_INL_H_
#define V8_OBJECTS_JS_WEAK_REFS_INL_H_

#include "src/objects/js-weak-refs.h"

#include "src/api-inl.h"
#include "src/heap/heap-write-barrier-inl.h"
#include "src/objects/microtask-inl.h"
#include "src/objects/smi-inl.h"

// Has to be the last include (doesn't have include guards):
#include "src/objects/object-macros.h"

namespace v8 {
namespace internal {

    OBJECT_CONSTRUCTORS_IMPL(WeakCell, HeapObject)
    OBJECT_CONSTRUCTORS_IMPL(JSWeakRef, JSObject)
    OBJECT_CONSTRUCTORS_IMPL(JSFinalizationGroup, JSObject)
    OBJECT_CONSTRUCTORS_IMPL(JSFinalizationGroupCleanupIterator, JSObject)
    OBJECT_CONSTRUCTORS_IMPL(FinalizationGroupCleanupJobTask, Microtask)

    ACCESSORS(JSFinalizationGroup, native_context, NativeContext,
        kNativeContextOffset)
    ACCESSORS(JSFinalizationGroup, cleanup, Object, kCleanupOffset)
    ACCESSORS(JSFinalizationGroup, active_cells, Object, kActiveCellsOffset)
    ACCESSORS(JSFinalizationGroup, cleared_cells, Object, kClearedCellsOffset)
    ACCESSORS(JSFinalizationGroup, key_map, Object, kKeyMapOffset)
    SMI_ACCESSORS(JSFinalizationGroup, flags, kFlagsOffset)
    ACCESSORS(JSFinalizationGroup, next, Object, kNextOffset)
    CAST_ACCESSOR(JSFinalizationGroup)

    ACCESSORS(WeakCell, finalization_group, Object, kFinalizationGroupOffset)
    ACCESSORS(WeakCell, target, HeapObject, kTargetOffset)
    ACCESSORS(WeakCell, holdings, Object, kHoldingsOffset)
    ACCESSORS(WeakCell, next, Object, kNextOffset)
    ACCESSORS(WeakCell, prev, Object, kPrevOffset)
    ACCESSORS(WeakCell, key, Object, kKeyOffset)
    ACCESSORS(WeakCell, key_list_next, Object, kKeyListNextOffset)
    ACCESSORS(WeakCell, key_list_prev, Object, kKeyListPrevOffset)
    CAST_ACCESSOR(WeakCell)

    CAST_ACCESSOR(JSWeakRef)
    ACCESSORS(JSWeakRef, target, HeapObject, kTargetOffset)

    ACCESSORS(JSFinalizationGroupCleanupIterator, finalization_group,
        JSFinalizationGroup, kFinalizationGroupOffset)
    CAST_ACCESSOR(JSFinalizationGroupCleanupIterator)

    ACCESSORS(FinalizationGroupCleanupJobTask, finalization_group,
        JSFinalizationGroup, kFinalizationGroupOffset)
    CAST_ACCESSOR(FinalizationGroupCleanupJobTask)

    void JSFinalizationGroup::Register(
        Handle<JSFinalizationGroup> finalization_group, Handle<JSReceiver> target,
        Handle<Object> holdings, Handle<Object> key, Isolate* isolate)
    {
        Handle<WeakCell> weak_cell = isolate->factory()->NewWeakCell();
        weak_cell->set_finalization_group(*finalization_group);
        weak_cell->set_target(*target);
        weak_cell->set_holdings(*holdings);
        weak_cell->set_prev(ReadOnlyRoots(isolate).undefined_value());
        weak_cell->set_next(ReadOnlyRoots(isolate).undefined_value());
        weak_cell->set_key(*key);
        weak_cell->set_key_list_prev(ReadOnlyRoots(isolate).undefined_value());
        weak_cell->set_key_list_next(ReadOnlyRoots(isolate).undefined_value());

        // Add to active_cells.
        weak_cell->set_next(finalization_group->active_cells());
        if (finalization_group->active_cells()->IsWeakCell()) {
            WeakCell::cast(finalization_group->active_cells())->set_prev(*weak_cell);
        }
        finalization_group->set_active_cells(*weak_cell);

        if (!key->IsUndefined(isolate)) {
            Handle<ObjectHashTable> key_map;
            if (finalization_group->key_map()->IsUndefined(isolate)) {
                key_map = ObjectHashTable::New(isolate, 1);
            } else {
                key_map = handle(ObjectHashTable::cast(finalization_group->key_map()), isolate);
            }

            Object value = key_map->Lookup(key);
            if (value->IsWeakCell()) {
                WeakCell existing_weak_cell = WeakCell::cast(value);
                existing_weak_cell->set_key_list_prev(*weak_cell);
                weak_cell->set_key_list_next(existing_weak_cell);
            } else {
                DCHECK(value->IsTheHole(isolate));
            }
            key_map = ObjectHashTable::Put(key_map, key, weak_cell);
            finalization_group->set_key_map(*key_map);
        }
    }

    void JSFinalizationGroup::Unregister(
        Handle<JSFinalizationGroup> finalization_group, Handle<Object> key,
        Isolate* isolate)
    {
        // Iterate through the doubly linked list of WeakCells associated with the
        // key. Each WeakCell will be in the "active_cells" or "cleared_cells" list of
        // its FinalizationGroup; remove it from there.
        if (!finalization_group->key_map()->IsUndefined(isolate)) {
            Handle<ObjectHashTable> key_map = handle(ObjectHashTable::cast(finalization_group->key_map()), isolate);
            Object value = key_map->Lookup(key);
            Object undefined = ReadOnlyRoots(isolate).undefined_value();
            while (value->IsWeakCell()) {
                WeakCell weak_cell = WeakCell::cast(value);
                weak_cell->RemoveFromFinalizationGroupCells(isolate);
                value = weak_cell->key_list_next();
                weak_cell->set_key_list_prev(undefined);
                weak_cell->set_key_list_next(undefined);
            }
            bool was_present;
            key_map = ObjectHashTable::Remove(isolate, key_map, key, &was_present);
            finalization_group->set_key_map(*key_map);
        }
    }

    bool JSFinalizationGroup::NeedsCleanup() const
    {
        return cleared_cells()->IsWeakCell();
    }

    bool JSFinalizationGroup::scheduled_for_cleanup() const
    {
        return ScheduledForCleanupField::decode(flags());
    }

    void JSFinalizationGroup::set_scheduled_for_cleanup(
        bool scheduled_for_cleanup)
    {
        set_flags(ScheduledForCleanupField::update(flags(), scheduled_for_cleanup));
    }

    Object JSFinalizationGroup::PopClearedCellHoldings(
        Handle<JSFinalizationGroup> finalization_group, Isolate* isolate)
    {
        Handle<WeakCell> weak_cell = handle(WeakCell::cast(finalization_group->cleared_cells()), isolate);
        DCHECK(weak_cell->prev()->IsUndefined(isolate));
        finalization_group->set_cleared_cells(weak_cell->next());
        weak_cell->set_next(ReadOnlyRoots(isolate).undefined_value());

        if (finalization_group->cleared_cells()->IsWeakCell()) {
            WeakCell cleared_cells_head = WeakCell::cast(finalization_group->cleared_cells());
            DCHECK_EQ(cleared_cells_head->prev(), *weak_cell);
            cleared_cells_head->set_prev(ReadOnlyRoots(isolate).undefined_value());
        } else {
            DCHECK(finalization_group->cleared_cells()->IsUndefined(isolate));
        }

        // Also remove the WeakCell from the key_map (if it's there).
        if (!weak_cell->key()->IsUndefined(isolate)) {
            if (weak_cell->key_list_prev()->IsUndefined(isolate) && weak_cell->key_list_next()->IsUndefined(isolate)) {
                // weak_cell is the only one associated with its key; remove the key
                // from the hash table.
                Handle<ObjectHashTable> key_map = handle(ObjectHashTable::cast(finalization_group->key_map()), isolate);
                Handle<Object> key = handle(weak_cell->key(), isolate);
                bool was_present;
                key_map = ObjectHashTable::Remove(isolate, key_map, key, &was_present);
                DCHECK(was_present);
                finalization_group->set_key_map(*key_map);
            } else if (weak_cell->key_list_prev()->IsUndefined()) {
                // weak_cell is the list head for its key; we need to change the value of
                // the key in the hash table.
                Handle<ObjectHashTable> key_map = handle(ObjectHashTable::cast(finalization_group->key_map()), isolate);
                Handle<Object> key = handle(weak_cell->key(), isolate);
                Handle<WeakCell> next = handle(WeakCell::cast(weak_cell->key_list_next()), isolate);
                DCHECK_EQ(next->key_list_prev(), *weak_cell);
                next->set_key_list_prev(ReadOnlyRoots(isolate).undefined_value());
                weak_cell->set_key_list_next(ReadOnlyRoots(isolate).undefined_value());
                key_map = ObjectHashTable::Put(key_map, key, next);
                finalization_group->set_key_map(*key_map);
            } else {
                // weak_cell is somewhere in the middle of its key list.
                WeakCell prev = WeakCell::cast(weak_cell->key_list_prev());
                prev->set_key_list_next(weak_cell->key_list_next());
                if (!weak_cell->key_list_next()->IsUndefined()) {
                    WeakCell next = WeakCell::cast(weak_cell->key_list_next());
                    next->set_key_list_prev(weak_cell->key_list_prev());
                }
            }
        }

        return weak_cell->holdings();
    }

    void WeakCell::Nullify(
        Isolate* isolate,
        std::function<void(HeapObject object, ObjectSlot slot, Object target)>
            gc_notify_updated_slot)
    {
        // Remove from the WeakCell from the "active_cells" list of its
        // JSFinalizationGroup and insert it into the "cleared_cells" list. This is
        // only called for WeakCells which haven't been unregistered yet, so they will
        // be in the active_cells list. (The caller must guard against calling this
        // for unregistered WeakCells by checking that the target is not undefined.)
        DCHECK(target()->IsJSReceiver());
        set_target(ReadOnlyRoots(isolate).undefined_value());

        JSFinalizationGroup fg = JSFinalizationGroup::cast(finalization_group());
        if (prev()->IsWeakCell()) {
            DCHECK_NE(fg->active_cells(), *this);
            WeakCell prev_cell = WeakCell::cast(prev());
            prev_cell->set_next(next());
            gc_notify_updated_slot(prev_cell, prev_cell.RawField(WeakCell::kNextOffset),
                next());
        } else {
            DCHECK_EQ(fg->active_cells(), *this);
            fg->set_active_cells(next());
            gc_notify_updated_slot(
                fg, fg.RawField(JSFinalizationGroup::kActiveCellsOffset), next());
        }
        if (next()->IsWeakCell()) {
            WeakCell next_cell = WeakCell::cast(next());
            next_cell->set_prev(prev());
            gc_notify_updated_slot(next_cell, next_cell.RawField(WeakCell::kPrevOffset),
                prev());
        }

        set_prev(ReadOnlyRoots(isolate).undefined_value());
        Object cleared_head = fg->cleared_cells();
        if (cleared_head->IsWeakCell()) {
            WeakCell cleared_head_cell = WeakCell::cast(cleared_head);
            cleared_head_cell->set_prev(*this);
            gc_notify_updated_slot(cleared_head_cell,
                cleared_head_cell.RawField(WeakCell::kPrevOffset),
                *this);
        }
        set_next(fg->cleared_cells());
        gc_notify_updated_slot(*this, RawField(WeakCell::kNextOffset), next());
        fg->set_cleared_cells(*this);
        gc_notify_updated_slot(
            fg, fg.RawField(JSFinalizationGroup::kClearedCellsOffset), *this);
    }

    void WeakCell::RemoveFromFinalizationGroupCells(Isolate* isolate)
    {
        // Remove the WeakCell from the list it's in (either "active_cells" or
        // "cleared_cells" of its JSFinalizationGroup).

        // It's important to set_target to undefined here. This guards that we won't
        // call Nullify (which assumes that the WeakCell is in active_cells).
        DCHECK(target()->IsUndefined() || target()->IsJSReceiver());
        set_target(ReadOnlyRoots(isolate).undefined_value());

        JSFinalizationGroup fg = JSFinalizationGroup::cast(finalization_group());
        if (fg->active_cells() == *this) {
            DCHECK(prev()->IsUndefined(isolate));
            fg->set_active_cells(next());
        } else if (fg->cleared_cells() == *this) {
            DCHECK(!prev()->IsWeakCell());
            fg->set_cleared_cells(next());
        } else {
            DCHECK(prev()->IsWeakCell());
            WeakCell prev_cell = WeakCell::cast(prev());
            prev_cell->set_next(next());
        }
        if (next()->IsWeakCell()) {
            WeakCell next_cell = WeakCell::cast(next());
            next_cell->set_prev(prev());
        }
        set_prev(ReadOnlyRoots(isolate).undefined_value());
        set_next(ReadOnlyRoots(isolate).undefined_value());
    }

} // namespace internal
} // namespace v8

#include "src/objects/object-macros-undef.h"

#endif // V8_OBJECTS_JS_WEAK_REFS_INL_H_
